﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Drawing;

/// <summary>
/// Summary description for WorldTileProjection
/// </summary>
public class WorldTileProjection
{
    private static readonly double  CIRCUMFERENCE_IN_METERS = 40075160.0;
    private static readonly int     IMAGE_SIZE = 256;
    private static readonly int     LEVEL_MAX  = 19;
    private static readonly int     LEVEL_MIN  = 5;
    private static readonly double  PI  = 3.14159265358979323846;
 
    private double m_dCircumference;
	private double m_dRadius;
	private Point2D m_Center;

	private int m_nTiles;
	private int m_nWidth;
	private int m_nHeight;
	private int m_nLevel;

	Rect2D m_rView;

	public WorldTileProjection()
    {
	    m_nTiles		    = 0;
	    m_dCircumference	= 0.0;
	    m_dRadius			= 0;
	    m_nWidth		    = 0;
	    m_nHeight		    = 0;
	    m_nLevel		    = 17;
	    m_Center		    = new Point2D(106.695970,10.7825867);
        m_rView             = new Rect2D();
    }

    //////////////////////////////////////////////////////////////////////////
    // Public Function
    //////////////////////////////////////////////////////////////////////////
    public int GetMinLevel()
    {
	    return LEVEL_MIN;
    }

    public int GetMaxLevel()
    {
	    return LEVEL_MAX;
    }

    public bool IsValidLevel(int nLevel)
    {
	    return !((nLevel > LEVEL_MAX) || (nLevel < LEVEL_MIN));
    }

    public void SetLevel(int nLevel)
    {
	    m_nLevel = nLevel;

	    if(nLevel > LEVEL_MAX)
		    m_nLevel = LEVEL_MAX;
	    else if(nLevel < LEVEL_MIN)
		    m_nLevel = LEVEL_MIN;

	    m_nTiles = 1 << nLevel;
	    m_dCircumference = IMAGE_SIZE * m_nTiles;
	    m_dRadius = m_dCircumference / (2.0 * PI);
    }

    public void SetRectView(Rect2D rcView)
    {
	    m_rView.SetRect(rcView.left,rcView.top,rcView.right,rcView.bottom);

	    m_nWidth = (int)rcView.Width();
	    m_nHeight = (int)rcView.Height();
    }

    public void SetCenter(double x, double y)
    {
	    m_Center.Set(x, y);
    }

    public void SetCenter(Point2D pt)
    {
	    m_Center.Set(pt);
    }

    public int GetWidthView()
    {
        return m_nWidth; 
    }

    public int GetHeightView()
    {
        return m_nHeight;
    }

    public int GetLevel()
    {
        return m_nLevel;
    }

    public Rect2D GetViewRect()
    {
        return m_rView;
    }

    public Point2D GetCenterPoint()
    {
        return m_Center;
    }

    public Point2D PDisplay2RP(int x, int y)
    {
	    Point2D c = RP2VP(m_Center);
	    c.x = c.x - (m_nWidth / 2) + x;
	    c.y = c.y - (m_nHeight / 2) + y;
	    return VP2RP(c);
    }

    public Point2D PDisplay2RP(Point2D pt)
    {
	    return PDisplay2RP((int)pt.x, (int)pt.y);
    }

    public void PDisplay2RP(Point2D[] vPt, Point2D[] wPt, int nSize)
    {
	    Point2D c = RP2VP(m_Center);
	    Point2D p = new Point2D();
    	
	    for(int i = 0; i < nSize; i++)
	    {
		    p.x = c.x - (m_nWidth / 2) + vPt[i].x;
		    p.y = c.y - (m_nHeight / 2) + vPt[i].y;
		    wPt[i] = VP2RP(p);
	    }
    }

    public void PDisplay2RP(Point[] vPt, Point2D[] wPt, int nSize)
    {
	    Point2D c = RP2VP(m_Center);
	    Point2D p = new Point2D();
    	
	    for(int i = 0; i < nSize; i++)
	    {
		    p.x = c.x - (m_nWidth / 2) + vPt[i].X;
		    p.y = c.y - (m_nHeight / 2) + vPt[i].Y;
		    wPt[i] = VP2RP(p);
	    }
    }

    /*public void PDisplay2RP(Point[] vPt, Point2D[] wPt, int nSize)
    {
	    Point2D c = RP2VP(m_Center);
	    Point2D p;

	    for(int i = 0; i < nSize; i++)
	    {
		    p.x = c.x - (m_nWidth / 2) + vPt[i].X;
		    p.y = c.y - (m_nHeight / 2) + vPt[i].Y;
		    wPt[i] = VP2RP(p);
	    }
    }*/


    public Point2D RP2PDisplay(double lon, double lat)
    {
	    Point2D ret = new Point2D();
	    Point2D p = RP2VP(lon, lat);
	    Point2D c = RP2VP(m_Center);
    	
	    ret.x = p.x - c.x + (m_nWidth / 2);
	    ret.y = p.y - c.y + (m_nHeight / 2);
    	
	    return ret;
    }

    public void RP2PDisplay(Point2D[] wPt, Point2D[] vPt, int nSize)
    {
	    Point2D c = RP2VP(m_Center);
	    Point2D p;

	    for(int i = 0; i < nSize; i++)
	    {
		    p = RP2VP(wPt[i]);

		    vPt[i].x = p.x - c.x + (m_nWidth / 2);
		    vPt[i].y = p.y - c.y + (m_nHeight / 2);
	    }
    }

    public void RP2PDisplay(Point2D[] wPt, Point[] vPt, int nSize)
    {
	    Point2D c = RP2VP(m_Center);
	    Point2D p = new Point2D();

	    for(int i = 0; i < nSize; i++)
	    {
		    p = RP2VP(wPt[i]);

		    vPt[i].X = (int)(p.x - c.x + (m_nWidth / 2));
            vPt[i].Y = (int)(p.y - c.y + (m_nHeight / 2));
	    }
    }

    /*public void RP2PDisplay(Point2D[] wPt, Point[] vPt, int nSize)
    {
	    Point2D c = RP2VP(m_Center);
	    Point2D p;

	    for(int i = 0; i < nSize; i++)
	    {
		    p = RP2VP(wPt[i]);

		    vPt[i].X = (long)(p.x - c.x + (m_nWidth / 2));
		    vPt[i].Y = (long)(p.y - c.y + (m_nHeight / 2));
	    }
    }*/


    public void RP2PDisplay(Point2D wPt, Point vPt)
    {
	    Point2D p = RP2VP(wPt.x, wPt.y);
	    Point2D c = RP2VP(m_Center);
    	
	    vPt.X = (int)(p.x - c.x + (m_nWidth / 2));
        vPt.Y = (int)(p.y - c.y + (m_nHeight / 2));
    }

    public Point2D RP2PDisplay(Point2D pt)
    {
	    return RP2PDisplay(pt.x, pt.y);
    }

    public Rect2D RDisplay2RR(Rect2D vRect)
    {
        Rect2D rRect = new Rect2D();

	    Point2D rpLT = PDisplay2RP((int)vRect.left,(int)vRect.top);
	    Point2D rpRB = PDisplay2RP((int)vRect.right,(int)vRect.bottom);

	    rRect.left = rpLT.x;
	    rRect.top = rpLT.y;
	    rRect.right = rpRB.x;
	    rRect.bottom = rpRB.y;

	    return rRect;
    }
    
    public Rect2D RDisplay2RR(Rectangle vRect)
    {
	    Rect2D rRect = new Rect2D();

	    Point2D rpLT = PDisplay2RP(vRect.Left, vRect.Top);
	    Point2D rpRB = PDisplay2RP(vRect.Right, vRect.Bottom);

	    rRect.left = rpLT.x;
	    rRect.top = rpLT.y;
	    rRect.right = rpRB.x;
	    rRect.bottom = rpRB.y;

	    return rRect;
    }

    public void RDisplay2RR(Rect2D vRect, 
									       double dLeft, double dTop, 
									       double dRight, double dBottom)
    {
	    Point2D rpLT = PDisplay2RP((int)vRect.left,(int)vRect.top);
	    Point2D rpRB = PDisplay2RP((int)vRect.right,(int)vRect.bottom);

	    dLeft = rpLT.x;
	    dTop = rpLT.y;
	    dRight = rpRB.x;
	    dBottom = rpRB.y;
    }

    public Rect2D RR2RDisplay(Rect2D rRect)
    {
        Rect2D vRect = new Rect2D();

	    Point2D rpLT = RP2PDisplay(rRect.left,rRect.top);
	    Point2D rpRB = RP2PDisplay(rRect.right,rRect.bottom);

	    vRect.left = rpLT.x;
	    vRect.top = rpLT.y;
	    vRect.right = rpRB.x;
	    vRect.bottom = rpRB.y;

	    return vRect;
    }

    public void RR2RDisplay(Rect2D rRect, Rectangle vRect)
    {
	    Point2D rpLT = RP2PDisplay(rRect.left,rRect.top);
	    Point2D rpRB = RP2PDisplay(rRect.right,rRect.bottom);

	    vRect.X   = (int)rpLT.x;
        vRect.Y = (int)rpLT.y;
        vRect.Width = (int)(rpRB.x - rpLT.x);
        vRect.Height = (int)(rpRB.y - rpLT.y);
    }

    public void RR2RDisplay(Rect2D rRect, 
						       double dLeft, double dTop, 
						       double dRight, double dBottom)
    {
	    Rect2D vRect = new Rect2D();

	    Point2D rpLT = RP2PDisplay(rRect.left,rRect.top);
	    Point2D rpRB = RP2PDisplay(rRect.right,rRect.bottom);

	    dLeft = rpLT.x;
	    dTop = rpLT.y;
	    dRight = rpRB.x;
	    dBottom = rpRB.y;
    }

    public double Meters2Pixels(double meters)
    {
	    return meters * m_dCircumference / CIRCUMFERENCE_IN_METERS;
    }

    public Point GetTileCoords(double lon, double lat)
    {
	    Point ret = new Point();
    	
	    double _tan, _log, _cos;
    	
	    _tan = Math.Tan(lat * PI / 180);
	    _cos = Math.Cos(lat * PI / 180);
	    _log = Math.Log(_tan + 1.0 / _cos);
    	
	    ret.X = (int)((lon + 180) / 360.0 * m_nTiles);
	    ret.Y = (int)((1.0 - _log / PI) / 2 * m_nTiles); 
    	
	    return ret;
    }

    public Point GetTileCoords(Point2D pt)
    {
	    return GetTileCoords(pt.x, pt.y);
    }

    //////////////////////////////////////////////////////////////////////////
    // Public Function
    //////////////////////////////////////////////////////////////////////////
    public Point2D RP2VP(double lon, double lat)
    {
	    Point2D ret = new Point2D();
	    double x, y;
	    double _sin;

	    x = lon * PI / 180;
	    y = lat * PI / 180;
	    _sin = Math.Sin(y);
    	
	    ret.x = (int)(m_dRadius * x + (m_dCircumference / 2.0));
	    ret.y = (int)((m_dCircumference / 2.0) - 
		    (m_dRadius / 2.0 * Math.Log((1.0 + _sin) / (1.0 - _sin))));
    	
	    return ret;
    }

    public Point2D RP2VP(Point2D pt)
    {
	    return RP2VP(pt.x, pt.y);
    }
    	
    public Point2D VP2RP(double x, double y)
    {
        Point2D ret = new Point2D();
	    double dbRadians = (x - (m_dCircumference / 2.0)) / m_dRadius;
	    double dbDegress = dbRadians * 180 / PI;
    	
	    double _exp = Math.Exp((y - (m_dCircumference / 2.0)) / m_dRadius);
	    double temp = (PI / 2.0) - (2.0 * Math.Atan(_exp));
    	
	    ret.x = dbDegress;
	    ret.y = temp * 180 / PI;
    	
	    return ret;
    }

    public Point2D VP2RP(Point2D pt)
    {
	    return VP2RP(pt.x, pt.y);	
    }
}
